﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using EnvDTE;
using Expect.Core.Config;
using Expect.VisualStudio.Extensions.Services;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Expect.Core;
using Expect.Core.BaseClasses;

namespace Expect.VisualStudio.Extensions
{

    //var dte2 = (EnvDTE80.DTE2)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE));
    //            Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte2;
    //            Microsoft.VisualStudio.Shell.ServiceProvider serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(sp);
    // Microsoft.VisualStudio.Shell.VsShellUtilities.GetProject()

    public class ExpectVsService : IExpectVSService, SExpectVSService
    {
        private ExpectItemResolution _cachedItemResolution;

        private SelectionResolution _lastResolution;
        private DateTime _lastResolutionDate;

        private DateTime fileTimeStamp;

        //private List<string> _fixtureClassHints;

        private EnvDTE80.DTE2 _dte;

        private IExpectPackage _pkg;

        public INameProvider NameProvider { get; private set; }

        public event EventHandler OnSelectedFileChanged;

        public ExpectVsService(IExpectPackage pkg)
        {
            _pkg = pkg;
            _dte = (EnvDTE80.DTE2)_pkg.GetVSService(typeof(SDTE));
            NameProvider = new DefaultNameProvider();
        }

        public SelectionResolution GetResolutionForLine(int linenumber)
        {
            string filename = null;
            filename = _cachedItemResolution.FileName;
            //string filename = this.CurrentFile().ToLower();

            if (!filename.EndsWith(Constants.ExpectFileExtension)) return null;
            return ProcessCurrentResolution(filename, linenumber);
        }

        public SelectionResolution GetCurrentSelectionResolution()
        {
            if (_cachedItemResolution == null)
                return null;

            string filename = null;
            filename = _cachedItemResolution.FileName;
            //string filename = this.CurrentFile().ToLower();

            if (!filename.EndsWith(Constants.ExpectFileExtension)) return null;
            return ProcessCurrentResolution(filename);
        }

        public ExpectItemResolution GetCurrentItemResolution()
        {
            string filename = this.CurrentFile();

            if (filename == null) 
                return null;

            filename = filename.ToLower();

            if (!filename.EndsWith(Constants.ExpectFileExtension)) return null;

            TimeSpan ts = DateTime.Now - fileTimeStamp;

            var item = _dte.Solution.FindProjectItem(filename);
            
            if (_cachedItemResolution == null || _cachedItemResolution.Item != item)
            {
                ProcessCurrentItemResolution(item);
            }

            return _cachedItemResolution;
        }

        public void ClearCurrentItem(string filename)
        {
            _cachedItemResolution = null;
            _lastResolution = null;
            _lastResolutionDate = DateTime.MinValue;

            UpdateClassMap(filename);
        }

        public void UpdateClassMap(string filename)
        {
            if (filename.EndsWith(".cs") && !filename.EndsWith(Constants.ExpectFileExtension + ".cs"))
            {
                var item = this._dte.Solution.FindProjectItem(filename);
                var prj = item.ContainingProject;

                if (this._classesCache.ContainsKey(prj))
                {
                    var classes = item.FileCodeModel.CodeElements.GetClasses();
                    List<ClassMap> maps = this._classesCache[prj];

                    foreach (var codeClass in classes)
                    {
                        var map = codeClass.ToClassMap();
                        var registered = maps.Find(m => m.FullName == map.FullName);
                        if (registered != null)
                        {
                            maps.Remove(registered);
                        }
                        maps.Add(map);
                    }
                    this._classesCache[prj] = maps;
                }
            }
        }

        private void ProcessCurrentItemResolution(ProjectItem item)
        {
            if (item != null)
            {                
                _cachedItemResolution = new ExpectItemResolution();
                _cachedItemResolution.Resolve(item.FileNames[0], item, this.GetClasses(item.ContainingProject, NameProvider), NameProvider);
                if (OnSelectedFileChanged != null)
                {
                    OnSelectedFileChanged(_cachedItemResolution, new EventArgs());
                }
                //RefreshTextView(item);
            }
        }

        private bool IsCurrentResolution(string filename, int linenumber)
        {
            if (_lastResolution == null)
                return false; 
            
            if (_lastResolution.Context.FileName != filename) 
                return false;

            if ((DateTime.Now - _lastResolutionDate).TotalMilliseconds > 100) 
                return false;

            if (_lastResolution.Selection.StartLine != linenumber) 
                return false;

            return true;
        }

        private SelectionResolution ProcessCurrentResolution(string filename)
        {
            var selection = GetActiveTextView(filename);
            
            if (this.IsCurrentResolution(filename, selection.StartLine)) 
                return _lastResolution;

            return ProcessCurrentResolution(filename, selection);
        }

        private SelectionResolution ProcessCurrentResolution(string filename, int linenumber)
        {
            var selection = GetActiveTextView(filename, linenumber);
            return ProcessCurrentResolution(filename, selection);
        }

        private SelectionResolution ProcessCurrentResolution(string filename,CurrentTextSelection selection)
        {
            if (this.IsCurrentResolution(filename, selection.StartLine))
                return _lastResolution;

            var res = new SelectionResolution(_pkg, this._cachedItemResolution);
            res.Selection = selection;
            if (res.Selection == null || res.Selection.Text == null)
                return null;
            res.Resolve();
            _lastResolution = res;
            _lastResolutionDate = DateTime.Now;

            return res;
        }

        private void RefreshTextView(ProjectItem item)
        {
            IVsTextView vTextView = null;
            vTextView = _dte.GetTextViewFor(item);            
            IVsTextLines lines = null;
            vTextView.GetBuffer(out lines);
            lines.Reload(1);
        }

        // get the active WpfTextView, if there is one.
        private CurrentTextSelection GetActiveTextView(string filename, int linenumber)
        {
            IVsTextView vTextView = null;
            vTextView = _dte.GetTextViewFor(filename);
            if (vTextView == null) return null;
            
            IVsTextLines lines = null;
            vTextView.GetBuffer(out lines);
            var res = new CurrentTextSelection();

            string fullselectiontext = null;
            int lastlineend = 0;
            lines.GetLengthOfLine(linenumber, out lastlineend);
            lines.GetLineText(linenumber, 0, linenumber, lastlineend, out fullselectiontext);
            res.Text = fullselectiontext;
            res.StartLine = linenumber;
            res.StartCol = 0;
            res.EndLine = linenumber;
            res.EndCol = lastlineend;

            int endLine, endCol;
            lines.GetLastLineIndex(out endLine, out endCol);
            string fulltext = null;
            lines.GetLineText(0, 0, endLine, endCol, out fulltext);
            res.FullText = fulltext;

            return res;
        }

        // get the active WpfTextView, if there is one.
        private CurrentTextSelection GetActiveTextView(string filename)
        {
            IVsTextView vTextView = null;
            vTextView = _dte.GetTextViewFor(filename);
            if (vTextView == null) return null;

            string selectionText = string.Empty;
            vTextView.GetSelectedText(out selectionText);
            int lineStart = 0;
            int colStart = 0;
            int lineEnd = 0;
            int colEnd = 0;

            vTextView.GetSelection(out lineStart, out colStart, out lineEnd, out colEnd);
            var res = new CurrentTextSelection();
            res.StartLine = lineStart;
            res.StartCol = colStart;
            res.EndLine = lineEnd;
            res.EndCol = colEnd;

            IVsTextLines lines = null;
            vTextView.GetBuffer(out lines);

            string fullselectiontext = null;
            int lastlineend = colStart;
            lines.GetLengthOfLine(lineEnd, out lastlineend);
            lines.GetLineText(lineStart, 0, lineStart, lastlineend, out fullselectiontext);
            res.Text = fullselectiontext;

            int endLine, endCol;
            lines.GetLastLineIndex(out endLine, out endCol);
            string fulltext = null;
            lines.GetLineText(0, 0, endLine, endCol, out fulltext);
            res.FullText = fulltext;

            return res;
        }

        public string CurrentFile()
        {
            IntPtr hierarchyPtr, selectionContainerPtr;
            uint projectItemId;
            IVsMultiItemSelect mis;

            IVsMonitorSelection monitorSelection = (IVsMonitorSelection)_pkg.GetVSService(typeof(SVsShellMonitorSelection));

            monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr);

            if (hierarchyPtr != IntPtr.Zero)
            {
                IVsHierarchy hierarchy =
                    Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy;

                if (hierarchy != null)
                {
                    string filename = null;
                    /*
                    hierarchy.GetProperty(projectItemId, (int)__VSHPROPID.VSHPROPID_Name, out value);
                    string filename = this.GetProperty<string>(hierarchy, projectItemId, (int)__VSHPROPID.VSHPROPID_Name);
                    */

                    hierarchy.GetCanonicalName(projectItemId, out filename);

                    return filename.ToLower();
                }
            }

            return null;
        }

        private T GetProperty<T>(IVsHierarchy hie, uint item, int idx)
        {
            object value;

            hie.GetProperty(item, idx, out value);

            return (T)value;
        }

        public Core.Config.ExpectConfigurationSection GetConfigurationFor(EnvDTE.Project project)
        {
            foreach (ProjectItem projectItem in project.GetAllProjectItem().Where(SolutionHelpers.IsPhysicalFile))
            {
                var fileName = SolutionHelpers.GetRelativePath(projectItem.GetFileName(), Path.GetDirectoryName(project.FullName));

                if (Path.GetFileName(fileName).Equals("app.config", StringComparison.InvariantCultureIgnoreCase))
                {
                    return ExpectConfigurationSection.ReadFromContent(projectItem.GetFileContent());
                }
            }

            return null;
        }

        public void ExpectFileLoaded(string filename)
        {
            var item = _dte.Solution.FindProjectItem(filename);
            filename = item.FileNames[0];
            if (_cachedItemResolution == null || _cachedItemResolution.Item != item)
            {
                this.ClearCurrentItem("");

                ProcessCurrentItemResolution(item);

                //ProcessCurrentResolution(filename);
                //BuildFixtureClassHints(filename);
            }
        }        

        private void AddRes(List<Declaration> lst, Declaration decl)
        {
            if (!lst.Any(d => d.DisplayText == decl.DisplayText))
            {
                lst.Add(decl);
            }
        }

        public List<Declaration> GetIntellisenseDeclarations()
        {
            var resolution = GetCurrentSelectionResolution();
            var str = resolution.Selection.Text;
            var trimmed = str.TrimStart();
            var res = new List<Declaration>();

            if (trimmed.StartsWith("#"))
            {
                const string imagePrefix = "#img ";
                if (trimmed.StartsWith(imagePrefix))
                {
                    var dte2 = (EnvDTE80.DTE2)_pkg.GetVSService(typeof(SDTE));
                    var imgname = trimmed.Substring(imagePrefix.Length);
                    var prjpath = _cachedItemResolution.Item.ContainingProject.FileName;
                    prjpath = Path.GetDirectoryName(prjpath).ToLower() + "\\";
                    bool isVs2010 = dte2.Version == "10.0";

                    foreach (var imgpath in _cachedItemResolution.Item.ContainingProject.EmbeddedImages())
                    {
                        string tmp = imgpath.ToLower();
                        if (tmp.StartsWith(prjpath))
                        {
                            tmp = tmp.Substring(prjpath.Length);
                        }
                        if (!isVs2010)
                            AddRes(res, new Declaration(tmp));
                        else
                            AddRes(res, new Declaration(tmp, "#img " + tmp, tmp));
                    }
                }

                return res;
            }

            if (trimmed.StartsWith("@"))
            {
                foreach (var item in Constants.KeyWords)
                {
                    if (string.IsNullOrEmpty(str) || str == "@" || item.TagName.ToLower().StartsWith(str, StringComparison.OrdinalIgnoreCase))
                        AddRes(res, new Declaration(item.TagName));
                }
                return res;
            }

            if (resolution.FixtureResKind == FixtureResolutionKind.Class)
            {
                if (resolution.FixtureMap.MethodsHints.Count > 0)
                {
                    foreach (var hint in resolution.FixtureMap.MethodsHints)
                    {
                        if (string.IsNullOrEmpty(str) || hint.StartsWith(str, StringComparison.OrdinalIgnoreCase))
                            AddRes(res, new Declaration(hint));
                    }
                }
            }
            else if (resolution.FixtureResKind == FixtureResolutionKind.MethodWithFixtureData)
            {
            }
            else
            {

                if (resolution.Context.Container != null && resolution.Context.Container.MethodsHints.Count > 0)
                {
                    foreach (var methodHint in resolution.Context.Container.MethodsHints)
                    {
                        if (string.IsNullOrEmpty(str) || methodHint.StartsWith(str, StringComparison.OrdinalIgnoreCase))
                            AddRes(res, new Declaration(methodHint));
                    }
                }

                var fixtureClasses = resolution.Context.ClassHints().ToList();
                if (fixtureClasses != null && fixtureClasses.Count > 0)
                {
                    foreach (var hint in fixtureClasses)
                    {
                        if (string.IsNullOrEmpty(str) || hint.StartsWith(str, StringComparison.OrdinalIgnoreCase))
                            AddRes(res, new Declaration(hint));
                    }
                }
            }

            res.Sort((d1, d2) => string.Compare(d1.DisplayText, d2.DisplayText));

            return res;
        }

        Dictionary<Project, List<ClassMap>> _classesCache = new Dictionary<Project, List<ClassMap>>();

        private Dictionary<Project, DateTime> _classesCacheDate = new Dictionary<Project, DateTime>();

        public List<ClassMap> GetClasses(Project prj, INameProvider np)
        {
            if (!_classesCache.ContainsKey(prj))
            {
                var lst = prj.GetClasses().ToClassMap().ToList();
                foreach (var cm in lst)
                {
                    cm.Reduce(np);
                }
                _classesCache.Add(prj, lst);
                _classesCacheDate.Add(prj, DateTime.Now);
            }

            return _classesCache[prj];
        }
    }
}
